//
// (C) Copyright 2009 Irantha Suwandarathna (irantha@gmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using EffiProzDB.Lib;


namespace EffiProzDB
{

    /**
     * Implementation of SQL set functions (currently only aggregate functions).
     * This reduces temporary Object creation by SUM and AVG functions for
     * INTEGER and narrower types.
     *
     * @author fredt@users
     * @author boucherb@users
     * @version 1.7.3
     * @since 1.7.2
     *
     */
    public class SetFunction
    {

        private HashSet<object> distinctValues;
        private bool isDistinct;

        //
        private int setType;
        private int type;

        //
        private int count;

        //
        private bool every = true;
        private bool some = false;
        private long currentLong;
        private double currentDouble;
        private Decimal currentDecimal;
        private Object currentValue;

        public SetFunction(int setType, int type, bool isDistinct)
        {

            this.setType = setType;
            this.type = type;

            if (isDistinct)
            {
                this.isDistinct = true;
                distinctValues = new HashSet<object>();
            }

            if (setType == Expression.VAR_SAMP
                    || setType == Expression.STDDEV_SAMP)
            {
                this.sample = true;
            }
        }

        public void add(Session session, Object item)
        {

            if (item == null)
            {              
                return;
            }

            if (isDistinct && !distinctValues.Add(item))
            {
                return;
            }

            count++;

            switch (setType)
            {

                case Expression.COUNT:
                    return;

                case Expression.AVG:
                case Expression.SUM:
                    {
                        switch (type)
                        {

                            case Types.TINYINT:
                            case Types.SMALLINT:
                            case Types.INTEGER:
                                currentLong += Convert.ToInt32(item);

                                return;

                            case Types.BIGINT:
                                addLong(Convert.ToInt64(item));

                                return;

                            case Types.REAL:
                            case Types.FLOAT:
                            case Types.DOUBLE:
                                currentDouble += Convert.ToDouble(item);

                                return;

                            case Types.NUMERIC:
                            case Types.DECIMAL:
                                //if (currentDecimal == null)
                                //{
                                //    currentDecimal = Convert.ToDecimal(item);
                                //}
                                //else
                                //{
                                    currentDecimal =  currentDecimal + Convert.ToDecimal(item);
                                //}

                                return;

                            default:
                                throw Trace.error(Trace.SUM_OF_NON_NUMERIC);
                        }
                    }
                case Expression.MIN:
                    {
                        if (currentValue == null)
                        {
                            currentValue = item;

                            return;
                        }

                        if (Column.compare(
                                session.database.collation, currentValue, item,
                                type) > 0)
                        {
                            currentValue = item;
                        }

                        return;
                    }
                case Expression.MAX:
                    {
                        if (currentValue == null)
                        {
                            currentValue = item;

                            return;
                        }

                        if (Column.compare(
                                session.database.collation, currentValue, item,
                                type) < 0)
                        {
                            currentValue = item;
                        }

                        return;
                    }
                case Expression.EVERY:
                    if (!(item is bool))
                    {
                        throw Trace.error(Trace.WRONG_DATA_TYPE);
                    }

                    every &= ((bool)item);

                    return;

                case Expression.SOME:
                    if (!(item is bool))
                    {
                        throw Trace.error(Trace.WRONG_DATA_TYPE);
                    }

                    some |= ((bool)item);

                    return;

                case Expression.STDDEV_POP:
                case Expression.STDDEV_SAMP:
                case Expression.VAR_POP:
                case Expression.VAR_SAMP:
                    if (!(item is int || item is long || item is short || item is double
                        || item is float || item is decimal || item is byte))
                    {
                        throw Trace.error(Trace.WRONG_DATA_TYPE);
                    }

                    addDataPoint(item);

                    return;
            }
        }

        public Object getValue()
        {

            if (setType == Expression.COUNT)
            {
                return (count);
            }

            if (count == 0)
            {
                return null;
            }

            switch (setType)
            {

                case Expression.AVG:
                    {
                        switch (type)
                        {

                            case Types.TINYINT:
                            case Types.SMALLINT:
                            case Types.INTEGER:
                                return (currentLong / count);

                            case Types.BIGINT:
                                {
                                    long value = getLongSum() / ((long)count);

                                    return (value);
                                }
                            case Types.REAL:
                            case Types.FLOAT:
                            case Types.DOUBLE:
                                return (double)(currentDouble / count);

                            case Types.NUMERIC:
                            case Types.DECIMAL:
                                return  (decimal)currentDecimal / ((decimal) count);

                            default:
                                throw Trace.error(Trace.SUM_OF_NON_NUMERIC);
                        }
                    }
                case Expression.SUM:
                    {
                        switch (type)
                        {

                            case Types.TINYINT:
                            case Types.SMALLINT:
                            case Types.INTEGER:
                                return (currentLong);

                            case Types.BIGINT:
                                return new Decimal(getLongSum());

                            case Types.REAL:
                            case Types.FLOAT:
                            case Types.DOUBLE:
                                return (decimal) currentDouble;

                            case Types.NUMERIC:
                            case Types.DECIMAL:
                                return currentDecimal;

                            default:
                                throw Trace.error(Trace.SUM_OF_NON_NUMERIC);
                        }
                    }
                case Expression.MIN:
                case Expression.MAX:
                    return currentValue;

                case Expression.EVERY:
                    return every ? true
                                 : false;

                case Expression.SOME:
                    return some ? true
                                : false;

                case Expression.STDDEV_POP:
                case Expression.STDDEV_SAMP:
                    return getStdDev();

                case Expression.VAR_POP:
                case Expression.VAR_SAMP:
                    return getVariance();

                default:
                    throw Trace.error(Trace.INVALID_CONVERSION);
            }
        }

        /**
         * During parsing and before an instance of SetFunction is created,
         * getType is called with type parameter set to correct type when main
         * SELECT statements contain aggregates. It is called with Types.NULL
         * when SELECT statements within INSERT or UPDATE contian aggregates.
         *
         */
        public static int getType(int setType, int type)
        {

            switch (setType)
            {

                case Expression.COUNT:
                    return Types.INTEGER;

                case Expression.AVG:
                    {
                        switch (type)
                        {

                            case Types.TINYINT:
                            case Types.SMALLINT:
                            case Types.INTEGER:
                            case Types.BIGINT:
                                return Types.BIGINT;

                            case Types.REAL:
                            case Types.FLOAT:
                            case Types.DOUBLE:
                                return Types.DOUBLE;

                            case Types.NUMERIC:
                            case Types.DECIMAL:
                                return Types.DECIMAL;

                            default:
                                return Types.NULL;
                        }
                    }
                case Expression.SUM:
                    {
                        switch (type)
                        {

                            case Types.TINYINT:
                            case Types.SMALLINT:
                            case Types.INTEGER:
                                return Types.BIGINT;

                            case Types.BIGINT:
                                return Types.DECIMAL;

                            case Types.REAL:
                            case Types.FLOAT:
                            case Types.DOUBLE:
                                return Types.DOUBLE;

                            case Types.NUMERIC:
                            case Types.DECIMAL:
                                return Types.DECIMAL;

                            default:
                                return Types.NULL;
                        }
                    }
                case Expression.MIN:
                case Expression.MAX:
                    return type;

                case Expression.EVERY:
                case Expression.SOME:
                    return Types.BOOLEAN;

                case Expression.STDDEV_POP:
                case Expression.STDDEV_SAMP:
                case Expression.VAR_POP:
                case Expression.VAR_SAMP:
                    return Types.DOUBLE;

                default:
                    throw Trace.error(Trace.INVALID_CONVERSION);
            }
        }

        // long sum - originally a separate class

        /**
         * Maintain the sum of multiple long values without creating a new
         * long object for each addition.
         */
        public static long multiplier = (0x0000000100000000L);

        //        long bigint = long.ZERO;
        long hi;
        long lo;

        public void addLong(long value)
        {

            if (value == 0) { }
            else if (value > 0)
            {
                hi += value >> 32;
                lo += value & 0x00000000ffffffffL;
            }
            else
            {
                if (value == long.MinValue)
                {
                    hi -= 0x000000080000000L;
                }
                else
                {
                    long temp = ~value + 1;

                    hi -= temp >> 32;
                    lo -= temp & 0x00000000ffffffffL;
                }
            }

            //            bigint = bigint.add(long.valueOf(value));
        }

        public long getLongSum()
        {

            long biglo = (lo);
            long bighi = (hi);
            long result = (bighi*multiplier)  +(biglo);

            /*
                        if ( result.CompareTo(bigint) != 0 ){
                             throw Trace.error(Trace.GENERAL_ERROR, "longSum mismatch");
                        }
            */
            return result;
        }

        // end long sum
        // statistics support - written by Campbell
        // this section was orginally an independent class
        private double sk;
        private double vk;
        private long n;
        private bool initialized;
        private bool sample;

        private void addDataPoint(object x)
        {    // optimized

            double xi;
            double xsi;
            long nm1;

            if (x == null)
            {
                return;
            }

            xi = (double)x;

            if (!initialized)
            {
                n = 1;
                sk = xi;
                vk = 0.0;
                initialized = true;

                return;
            }

            n++;

            nm1 = (n - 1);
            xsi = (sk - (xi * nm1));
            vk += ((xsi * xsi) / n) / nm1;
            sk += xi;
        }

        private object getVariance()
        {

            if (!initialized)
            {
                return null;
            }

            return sample ? (n == 1) ? 0    // NULL (not NaN) is correct _in this case
                                     : (double)(vk / (double)(n - 1))
                          : (double)(vk / (double)(n));
        }

        private object getStdDev()
        {

            if (!initialized)
            {
                return null;
            }

            return sample ? ((n == 1) ? 0    // NULL (not NaN) is correct _in this case
                                     : (double)(Math.Sqrt(vk / (double)(n - 1))))
                          : (double)(Math.Sqrt(vk / (double)(n)));
        }

        // end statistics support
    }
}
